using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.WindowsAzure.Accelerator.Configuration;
using Microsoft.WindowsAzure.Accelerator.Diagnostics;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using CloudDrive = Microsoft.WindowsAzure.StorageClient.CloudDrive;

namespace Microsoft.WindowsAzure.Accelerator
{
    /// <summary>
    /// Controller class for all loading and managing accelerator configuration and processes.
    /// </summary>
    public static class ServiceManager
    {
#region | FIELDS

        private static CloudStorageAccount _cloudStorageAccount;
        private static readonly Dictionary<String, Variable> _variables = new Dictionary<String, Variable>();
        private static readonly Dictionary<String,String>    _environment = new Dictionary<String, String>();
        private static readonly Dictionary<String, String>   _environmentRollback = new Dictionary<String, String>();
        private static readonly List<Application>            _childApplications = new List<Application>();

        private static Settings _settings;
        private static String _serviceName;
        private static LocalResource _acceleratorLocalStorage;
        private static LocalResource _acceleratorCloudDriveStorage;
        private static WebServer _webServer;
        private static String _cloudSyncPath = null;
        private static Dictionary<String, CloudDrive> _drives;
        private static Dictionary<String, String> _drivePaths;
        private static Dictionary<String, IPAddress> _hostBindings;
                
#endregion
#region | PROPERTIES

        /// <summary>
        /// Gets the name of the current application role instance.
        /// </summary>
        public static String ServiceName
        {
            get { return _serviceName ?? (_serviceName = RoleEnvironment.IsAvailable ? String.Format("{0}_{1}", RoleEnvironment.CurrentRoleInstance.Role.Name, RoleEnvironment.CurrentRoleInstance.Id) : DefaultSettings.AcceleratorTracingName); } 
            private set { _serviceName = value; }
        }

        /// <summary>
        /// Gets the combined accelerator and azure configuration and service definition settings.
        /// </summary>
        /// <value>The settings.</value>
        public static Settings Settings
        {
            get { return _settings ?? (_settings = new Settings()); }
        }

        /// <summary>
        /// Gets the role instance number.
        /// </summary>
        /// <value>The role instance number.</value>
        public static String RoleInstanceNumber
        {
            get { return RoleEnvironment.CurrentRoleInstance.Id.Replace(RoleEnvironment.DeploymentId, ""); }
        }

        /// <summary>
        /// Gets or sets the overall service state.
        /// </summary>
        public static ServiceState ServiceState
        {
            get; 
            set;
        }

        /// <summary>
        /// Gets the list of intialized role applications. 
        /// </summary>
        public static List<Application> ChildApplications
        {
            get { return _childApplications; }
        }

        /// <summary>
        /// Gets the dictionary of all configuration variables.
        /// </summary>
        public static Dictionary<String, Variable> Variables
        {
            get { return _variables; }
        }

        /// <summary>
        /// Gets the environment variables to be configured for each applications.
        /// </summary>
        public static Dictionary<String, String> EnvironmentVariables
        {
            get { return _environment; }
        }

        /// <summary>
        /// Gets the global dictionary of accelerator mounted cloud drives (by uri).
        /// </summary>
        public static Dictionary<String, CloudDrive> CloudDrives
        {
            get { return _drives ?? ( _drives = new Dictionary<String, CloudDrive>()); }
        }
        
        /// <summary>
        /// Gets the local drive paths; if more than one instance is running, this returns the local drive sync path.
        /// </summary>
        public static Dictionary<String, String> DrivePaths
        {
            get { return _drivePaths ?? ( _drivePaths = new Dictionary<String, String>()); }
        }

        /// <summary>
        /// Gets the host bindings as set by an application's sites.  Otherwise, this is not in use.
        /// </summary>
        public static Dictionary<String, IPAddress> HostBindings
        {
            get { return _hostBindings ?? (_hostBindings = new Dictionary<String, IPAddress>()); }
        }

        /// <summary>
        /// Gets the list of instances for the current role.
        /// </summary>
        public static List<RoleInstance> RoleInstances
        {
            get { return RoleEnvironment.CurrentRoleInstance.Role.Instances.ToList(); }
        }

        /// <summary>
        /// Gets the local storage area for use by the accelerators.  This is also the cache backing storage for all accelerator cloud drives.
        /// </summary>
        public static LocalResource LocalStorage
        {
            get { return _acceleratorLocalStorage ?? ( _acceleratorLocalStorage = RoleEnvironment.GetLocalResource("LocalStorage") ); }
        }

        /// <summary>
        /// Gets the accelerator local storage path.
        /// </summary>
        public static String LocalStoragePath
        {
            get { return LocalStorage.RootPath.TrimEnd('\\', '/'); }
        }

        /// <summary>
        /// Gets the local runtime web site hosting path in local storage.
        /// </summary>
        public static String CloudSyncPath
        {
            get
            {   if (_cloudSyncPath == null)
                {
                    _cloudSyncPath = Path.Combine(LocalStoragePath, "cloud-sync");
                    //if (!Directory.Exists(_cloudSyncPath))
                    //    Directory.CreateDirectory(_cloudSyncPath);
                }
                return _cloudSyncPath;
            }
        }

        /// <summary>
        /// Gets the local storage area for use by the accelerators.  This is also the cache backing storage for all accelerator cloud drives.
        /// </summary>
        public static LocalResource CloudDriveCacheStorage
        {
            get { return _acceleratorCloudDriveStorage ?? ( _acceleratorCloudDriveStorage = RoleEnvironment.GetLocalResource("CloudDriveCache") ); }
        }

        /// <summary>
        /// Gets the primary configuration file.
        /// </summary>
        private static XDocument XDefinitionsDocument
        {
            get; 
            set;
        }

        /// <summary>
        /// Gets an instance of the CloudStorageAccount.
        /// </summary>
        private static CloudStorageAccount CloudStorageAccount
        {
            get
            {
                if ( _cloudStorageAccount == null )
                    if ( !CloudStorageAccount.TryParse(ConnectionString, out _cloudStorageAccount) )
                        if ( IsDevStorageEnabled ) _cloudStorageAccount = CloudStorageAccount.DevelopmentStorageAccount;
                return _cloudStorageAccount;
            }
        }

        /// <summary>
        /// Gets the web server; creating the single allowable instance the first time used.
        /// </summary>
        public static WebServer WebServer
        {
            get
            {
                if (_webServer == null)
                    _webServer = new WebServer(LocalStoragePath)
                                     {
                                         MachineKey = GetConfigSetting("AcceleratorMachineKey").OnValid(s => s.Protect(x => XElement.Parse(x))),
                                         LoggingDirectory = DiagnosticsSettings.LogStoragePath
                                     };
                return _webServer;
            }
        }

        /// <summary>
        /// Gets the role's root path.
        /// </summary>
        public static String RoleRootPath
        {
            get { return Environment.GetEnvironmentVariable("RoleRoot").OnValid(ar => ar.Trim('\\', '/')); }
        }

        /// <summary>
        /// Gets the role's app root path.
        /// </summary>
        public static String AppRootPath
        {
            get { return RoleRootPath + @"\approot"; }
        }

        /// <summary>
        /// Gets the location of the application definitions blob (if in use).
        /// </summary>
        private static String DefinitionsBlobUri
        {
            get { return GetConfigSetting("AcceleratorConfigBlobUri"); } //x| DefaultSettings.CloudDriveUri; }  (we don't want a default value for this; if it is not defined it is not in use.)
        }

        /// <summary>
        /// Gets the Azure storage account connection string.
        /// </summary>
        private static String ConnectionString
        {
            get { return RoleEnvironment.GetConfigurationSettingValue("AcceleratorConnectionString"); }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is dev storage enabled.
        /// </summary>
        public static Boolean IsDevStorageEnabled
        {
            get { return !IsRunningInCloud && GetConfigSetting("EnableDevStorage").As<Boolean>(); }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is running in cloud.
        /// </summary>
        public static Boolean IsRunningInCloud
        {
            get { return !RoleEnvironment.DeploymentId.EndsWith(")"); /*i| hack, but for some things like forcing drive locks; we need to know the difference to prevent dev from stealing production accidentally. */ }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the cloud drive cache has already been initialized for this role instance.
        /// </summary>
        private static Boolean IsCloudDriveCacheInitialized
        {
            get; 
            set;
        }

#endregion
#region | EVENTS

        /// <summary>
        /// Loads the applications definitions from the configuration files.
        /// </summary>
        public static void InitializeServiceManager()
        {
            ServiceState = ServiceState.Healthy;
            
            //i| Load the accelerator services configuration.
            LogLevel.Information.Trace(ServiceName, "Initialization : Environment : Saving...");
            foreach ( DictionaryEntry kvp in Environment.GetEnvironmentVariables() )
                _environmentRollback[(String)kvp.Key] = kvp.Value as String;
            LogLevel.Information.Trace(ServiceName, "Initialization : Environment : Saved.");

            LogLevel.Information.Trace(ServiceName, "Initialization : Definitions : Loading...");
            XDefinitionsDocument = XDocument.Load(DefaultSettings.DefinitionsFilePath);
            XElement acceleratorConfig = XDefinitionsDocument.XPathSelectElement("serviceAccelerator/role");
            String roleRoot     = acceleratorConfig.OnValid(c => c.Element("roleRoot").GetAttribute("pathKey")) ?? "RoleRoot";
            String appRoot      = acceleratorConfig.OnValid(c => c.Element("appRoot").GetAttribute("pathKey")) ?? "AppRoot";
            String diagStorage  = acceleratorConfig.OnValid(c => c.Element("diagnoticsStorage").GetAttribute("pathKey")) ?? "DiagnosticLogsFolder";
            String accelStorage = acceleratorConfig.OnValid(c => c.Element("localStorage").GetAttribute("pathKey")) ?? "LocalStorage";
            String instanceId   = acceleratorConfig.OnValid(c => c.Element("instanceName").GetAttribute("pathKey")) ?? "InstanceId";
            Variables[roleRoot] = RoleRootPath;
            Variables[appRoot]      = AppRootPath;
            Variables[diagStorage]  = DiagnosticsSettings.LogStoragePath;
            Variables[accelStorage] = LocalStoragePath;
            Variables[instanceId] = RoleEnvironment.CurrentRoleInstance.Id;
            InitializeInstanceVariables();
            LogLevel.Information.Trace(ServiceName, "Initialization : Definitions : Loaded.");
        }

        /// <summary>
        /// Adds instance information to variables (making it available to all batch and powershell scripts).
        /// </summary>
        private static void InitializeInstanceVariables()
        {
            foreach (var role in RoleInstances)
                foreach (var endPoint in role.InstanceEndpoints)
                {
                    String key = String.Format("_R{0}_{1}", role.Id.Substring(role.Id.LastIndexOf('.')+1), endPoint.Key);
                    Variables[key] = endPoint.Value.IPEndpoint.ToString();
                }
        }

        /// <summary>
        /// Called when accelerator is starting.
        /// </summary>
        public static void Start()
        {
            //i|
            //i| Initialize the global service manager resources.
            //i| 
            try 
            {
                LogLevel.Information.Trace(ServiceName, "Initialization : Starting...");
                InitializeServiceManager();
                LogLevel.Information.Trace(ServiceName, "Initialization : Completed.");
            }
            catch (Exception ex) 
            {
                ServiceState = ServiceState.Faulted;
                LogLevel.Error.TraceException(ServiceName, ex, "Initialization : Failed : An exception occured while loading and parsing application configuration file.");
                return;
            }

            //i|
            //i| Load and configure applications.
            //i|
            try
            {
                LogLevel.Information.Trace(ServiceName, "Configuration : Starting...");
                LoadApplications();
                StartApplications();
                RunApplications();
                LogLevel.Information.Trace(ServiceName, "Configuration : Completed.");
            }
            catch ( Exception ex )
            {
                ServiceState = ServiceState.Faulted;
                LogLevel.Error.TraceException(ServiceName, ex, "Configuration : Failed : An unhandled exception occured while intitializing application dependencies.");
                return;
            }
        }

        /// <summary>
        /// Performs the tear down of application environment and configuration files using Azure runtime service values.
        /// </summary>
        public static void Stop()
        {
            //i| Start the tear-down processes; dependents first.
            foreach (Application application in ChildApplications)
            {
                LogLevel.Information.Trace(ServiceName, "Stopping {0}...", application.Name); 
                application.Protect(a => a.OnStop());
                LogLevel.Information.Trace(ServiceName, "{0} stopped.", application.Name);
            }

            //i| Stop the hosted web core if it was started.
            if ( _webServer != null )
            {
                LogLevel.Information.Trace(ServiceName, "Stopping hosted web server...");
                _webServer.Protect(ws => ws.Stop());
                _webServer = null;
                LogLevel.Information.Trace(ServiceName, "Hosted web server stopped.");
            }
            
            //i| Release any locks created on cloud drives. //f| good opportunity to create a snapshot too
            foreach ( var drive in CloudDrives.Values )
                drive.Protect(d => d.Unmount());
            CloudDrives.Clear();

            LogLevel.Information.Trace(ServiceName, "All Services Stopped.");
            //b| bug need to rollback environment variables.
            Variables.Clear();
            EnvironmentVariables.Clear();
            ChildApplications.Clear();
            _environmentRollback.Clear();
            XDefinitionsDocument = null;

            Thread.Sleep(5000);
        }

        /// <summary>
        /// Performs a warm reset of this instance.
        /// </summary>
        /// <remarks>
        /// Using either of the two diagnostics console implementation; this is very useful for changing a blob based configuration and trying it out without having to wait for a complete recycle of the role.
        /// </remarks>
        public static void Reset()
        {
            Stop();
            Start();
        }

        /// <summary>
        /// Runs this instance.
        /// </summary>
        public static void Run()
        {
            //i|
            //i| Load and configure applications.
            //i|
            try
            {
                LogLevel.Information.Trace(ServiceName, "OnRunning Applications : Starting...");
                OnRunningApplication();
                LogLevel.Information.Trace(ServiceName, "OnRunning Applications : Completed.");
                LogLevel.Information.Trace(ServiceName, "All runtime applications runnning...");
            }
            catch (Exception ex)
            {
                ServiceState = ServiceState.Faulted;
                LogLevel.Error.TraceException(ServiceName, ex, "Configuration : Failed : An unhandled exception occured while intitializing the runtime applications.");
                return;
            } 
        }
        
#region | APPLICATION 

        /// <summary>
        /// Manages the initialization and configuration of accelerator applications.
        /// </summary>
        private static void LoadApplications()
        {
            //i|
            //i| Active applications are defined in the CSCFG |x|rdm|depricated[( or if not there, under the accelerator.config role element )]
            //i|
            Dictionary<String, String> cloudApplications = RoleEnvironment.GetConfigurationSettingValue("AcceleratorApplication").OnValid(
                    setting => setting.SplitToDictionary<String, String>(new[] { ';' }, new[] { ',' }) 
                        ?? new Dictionary<String, String> { { setting, String.Empty } });

            if ( cloudApplications == null || cloudApplications.Count < 1 ) 
            {
                LogLevel.Error.Trace(ServiceName, "Configuration : Failure : Application startup setting not found. Verify that the setting AcceleratorApplication exists in the cloud configuration file, and that it includes the name and version of the configuration section for the application to deploy.");
                ServiceState = ServiceState.ConfigurationError;
                return;
            }

            //i| First, check for an explicitly defined custom application definition in blob storage.
            XDocument xdoc = null;
            if (!String.IsNullOrEmpty(DefinitionsBlobUri))
            {
                LogLevel.Information.Trace(ServiceName, "Initialization : Definitions : Attempting to load application definitions file from blob storage: {{ '{0}' }}.", DefinitionsBlobUri);
                xdoc = CloudStorageAccount.Protect(csa => XDocument.Parse(CloudStorageAccount.DownloadText(DefinitionsBlobUri)));
            }
            
            //i|
            //i| Create all applications (and any corresponding dependant applications).  Each application may be instantiated only once; 
            //i| regardless of mutiple explicit declarations or child-application dependency requirements.
            //i|)
            foreach ( var appKvp in cloudApplications )
            {
                XElement configSection = GetApplicationConfigurationSection(xdoc, appKvp.Key, appKvp.Value);
                String   name          = configSection.GetAttribute("name");
                String   version       = configSection.GetAttribute("version");
                if (!IsExistingApplication(name, version))
                {
                    ChildApplications.Add(new Application(configSection));
                }
            }
        }

        /// <summary>
        /// Starts the applications.
        /// </summary>
        private static void StartApplications()
        {
            //i|
            //i| Call the OnStart() event of each of the child application (just initialized above).
            //i|
            foreach ( Application application in ChildApplications )
                application.OnStart();
            WriteVariablesToTraceLog();
        }

        /// <summary>
        /// Manages the execution of accelerator processes.
        /// </summary>
        private static void RunApplications()
        {
            //i|
            //i| Start the processes (recursively dependents first).
            //i|
            foreach ( Application application in ChildApplications )
            {
                LogLevel.Information.Trace(ServiceName, "Starting '{0}'...", application.Name);
                try { application.OnRun(); LogLevel.Information.Trace(ServiceName, "'{0}' started.", application.Name); }
                catch ( Exception ex ) { LogLevel.Error.TraceException(ServiceName, ex, "An exception occured while starting '{0}'.", application.Name); }
            }

            //i|
            //i| Start the hosted web core if necessary.
            //i|
            if ( _webServer != null )
            {
                LogLevel.Information.Trace(ServiceName, "Starting hosted web server...");
                try { _webServer.Start(); LogLevel.Information.Trace(ServiceName, "Hosted web server started."); }
                catch (Exception ex) { LogLevel.Error.TraceException(ServiceName, ex, "An exception occured while starting the hosted web server."); }
            }


            LogLevel.Information.Trace(ServiceName, "All Services Running...");
        }

        /// <summary>
        /// Manages the execution of accelerator processes.
        /// </summary>
        private static void OnRunningApplication()
        {
            //i| 
            //i| Start any OnRunning() interval or maintenance processes. Use to intiated maintenance or interval processes (backups, snapshots, log file copy to diagnostics directories, etc.).
            //i|
            foreach (Application application in ChildApplications)
                try { application.OnRunning(); }
                catch (Exception ex) { LogLevel.Error.TraceException(ServiceName, ex, "An OnRunning() exception occured in '{0}'.", application.Name); }
        }


#endregion

        /// <summary>
        /// Gets the application definition from the configuration file if it exists.
        /// </summary>
        /// <param name="xDefinitions">The applications definitions document.</param>
        /// <param name="applicationName">Name of the application.</param>
        /// <param name="applicationVersion">The application version.</param>
        /// <returns></returns>
        private static XElement GetDefinition(XDocument xDefinitions, String applicationName, String applicationVersion)
        {
            if (xDefinitions == null)
                return null;
            var appSections = xDefinitions.XPathSelectElements(String.Format("serviceAccelerator/application[@name=\"{0}\"]", applicationName)).ToList();
            return appSections.Where(s => s.GetAttribute("version") == applicationVersion).FirstOrDefault() ?? appSections.FirstOrDefault();
        }

        /// <summary>
        /// Gets the application configuration section matching the name and version attributes of the supplied dependency element.
        /// </summary>
        /// <param name="xDefinition">An applications definitions document.</param>
        /// <param name="applicationName">Name of the application.</param>
        /// <param name="applicationVersion">The application version.</param>
        /// <returns>Application configuration section.</returns>
        public static XElement GetApplicationConfigurationSection(XDocument xDefinition, String applicationName, String applicationVersion)
        {
            XDocument xdoc = xDefinition;
            Func<String, XDocument> load = (path) =>
                                               {
                                                    if (!String.IsNullOrEmpty(path) && File.Exists(path))
                                                    {
                                                        if ((xdoc = path.Protect(p => XDocument.Load(p))) != null)
                                                            LogLevel.Information.Trace(ServiceName, "Initialization : Definitions : Loaded application definitions file: {{ '{0}' }}.", path);
                                                    }
                                                    return xdoc;
                                               };
            XElement xdef = 
                   GetDefinition(xdoc, applicationName, applicationVersion)  //i| Check for definition in existing context.
                ?? GetDefinition(load(Path.Combine(DefaultSettings.DefinitionsFolder, applicationName + ".config")), applicationName, applicationVersion) //i| Attempt to load using application specific file.
                ?? GetDefinition(XDefinitionsDocument, applicationName, applicationVersion); //i| Attempt to load using the default file.
            return xdef;
        }

        /// <summary>
        /// Gets the config setting from Azure or application settings.
        /// </summary>
        public static String GetConfigSetting(String settingName)
        {
            return settingName.Protect(n => RoleEnvironment.GetConfigurationSettingValue(n)) ?? System.Configuration.ConfigurationManager.AppSettings[settingName];
        }

        /// <summary>
        /// Determines whether an instance of the specified application already exists.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="version">The version.</param>
        public static Boolean IsExistingApplication(String name, String version)
        {
            foreach ( var a in ChildApplications )
                if ((a.Name == name && a.Version == version) || a.IsChildApplication(name, version))
                    return true;
            return false;
        }

        /// <summary>
        /// Determines whether the specified element is enabled. Default is true.
        /// </summary>
        /// <remarks>
        /// If the enabled attribute has not been defined; the default is true.
        /// If it has been defined and Variable contents resovle to empty; the value is false.  
        /// </remarks>
        public static Boolean IsEnabled(this XElement element)
        {
            return element.Attribute("enabled") == null 
                ? true
                : element.AttributeAsVariable("enabled", Boolean.FalseString);
        }

        /// <summary>
        /// Returns the elements attribute as a variable or the default if not exists.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="attributeName">Name of the attribute.</param>
        /// <param name="variableDefault">The variable default.</param>
        public static Variable AttributeAsVariable(this XElement element, String attributeName, String variableDefault)
        {
            return new Variable(element.GetAttribute(attributeName) ?? variableDefault);
        }

        /// <summary>
        /// Writes the variables to trace log.
        /// </summary>
        public static void WriteVariablesToTraceLog()
        {
            LogLevel.Verbose.TraceContent(ServiceName, EnvironmentVariables.ToTraceString("ENVIRONMENT VARIABLES"), "Environment Variables");
            LogLevel.Verbose.TraceContent(ServiceName, Variables.ToTraceString("SERVICE MANAGER VARIABLES"), "Application Definition Variables");
        }

        /// <summary>
        /// Initializes the global cloud drive cache.  (Cache is per-role instance; and not per-drive mounted.)
        /// </summary>
        public static void InitializeCloudDriveCache()
        {
            if (!IsCloudDriveCacheInitialized)
            {
                var path = CloudDriveCacheStorage.RootPath.TrimEnd('\\', '/');
                var size = CloudDriveCacheStorage.MaximumSizeInMegabytes;
                LogLevel.Information.Trace(ServiceName, "CloudDrive global cache initializing.\n\t{0:30}: {1}\n\t{2:30}: {3}\n\t{4:30}: {5}\n", "Cache Local Storage", path, "Cache Size", size, "Directory Exists", Directory.Exists(path));
                for (Int32 i = 30; i > 0; i--) //i| Per known Azure CloudDrive issue; Windows Service may need more time to spin up.
                    try
                    {
                        CloudDrive.InitializeCache(path, size);
                    }
                    catch (CloudDriveException ex)
                    {
                        if (!ex.Message.Equals("ERROR_UNSUPPORTED_OS") || i == 1)
                            throw;
                        Thread.Sleep(1000);
                    }

                IsCloudDriveCacheInitialized = true;
                LogLevel.Information.Trace(ServiceName, "CloudDrive global cache initialized.");
            }
        }

#endregion
    }

    /// <summary>
    /// Default settings for Azure accelerators.
    /// </summary>
    public static class DefaultSettings
    {
        public const String AcceleratorTracingName = "ServiceManager";
        public const String CloudDriveUri = @"cloud-drives/accelerator.vhd";
        public static String DefinitionsFolder { get { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Definitions"); } }
        public static String DefinitionsFilePath { get { return Path.Combine(DefinitionsFolder, "Applications.config"); } }
    }

    /// <summary>
    /// Status of application and services startup orchestration.
    /// </summary>
    public enum ServiceState
    {
        Healthy = 0,
        MissingDependency = 1,
        ConfigurationError = 3,
        Faulted = 5
    }
}